//
// experimental/detail/coro_traits.hpp
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
// Copyright (c) 2021-2022 Klemens D. Morgenstern
//                         (klemens dot morgenstern at gmx dot net)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//

#ifndef ASIO_EXPERIMENTAL_DETAIL_CORO_TRAITS_HPP
#define ASIO_EXPERIMENTAL_DETAIL_CORO_TRAITS_HPP

#if defined(_MSC_VER) && (_MSC_VER >= 1200)
# pragma once
#endif // defined(_MSC_VER) && (_MSC_VER >= 1200)

#include "asio/detail/config.hpp"
#include <optional>
#include <variant>
#include "asio/any_io_executor.hpp"

namespace asio {
    namespace experimental {
        namespace detail {

            template<class From, class To>
            concept convertible_to = std::is_convertible_v<From, To>;

            template<typename T>
            concept decays_to_executor = execution::executor < std::decay_t < T >>;

            template<typename T, typename Executor = any_io_executor>
            concept execution_context = requires(T & t) {
                { t.get_executor() } -> convertible_to<Executor>;
            };

            template<typename Yield, typename Return>
            struct coro_result {
                using type = std::variant<Yield, Return>;
            };

            template<typename Yield>
            struct coro_result<Yield, void> {
                using type = std::optional<Yield>;
            };

            template<typename Return>
            struct coro_result<void, Return> {
                using type = Return;
            };

            template<typename YieldReturn>
            struct coro_result<YieldReturn, YieldReturn> {
                using type = YieldReturn;
            };

            template<>
            struct coro_result<void, void> {
                using type = void;
            };

            template<typename Yield, typename Return>
            using coro_result_t = typename coro_result<Yield, Return>::type;

            template<typename Result, bool IsNoexcept>
            struct coro_handler;

            template<>
            struct coro_handler<void, false> {
                using type = void(std::exception_ptr);
            };

            template<>
            struct coro_handler<void, true> {
                using type = void();
            };

            template<typename T>
            struct coro_handler<T, false> {
                using type = void(std::exception_ptr, T);
            };

            template<typename T>
            struct coro_handler<T, true> {
                using type = void(T);
            };

            template<typename Result, bool IsNoexcept>
            using coro_handler_t = typename coro_handler<Result, IsNoexcept>::type;

        } // namespace detail

#if defined(GENERATING_DOCUMENTATION)

        /// The traits describing the resumable coroutine behaviour.
        /**
         * Template parameter @c Yield specifies type or signature used by co_yield,
         * @c Return specifies the type used for co_return, and @c Executor specifies
         * the underlying executor type.
         */
        template <typename Yield, typename Return, typename Executor>
        struct coro_traits
        {
          /// The value that can be passed into a symmetrical cororoutine. @c void if
          /// asymmetrical.
          using input_type = argument_dependent;

          /// The type that can be passed out through a co_yield.
          using yield_type = argument_dependent;

          /// The type that can be passed out through a co_return.
          using return_type = argument_dependent;

          /// The type received by a co_await or async_resume. It's a combination of
          /// yield and return.
          using result_type = argument_dependent;

          /// The signature used by the async_resume.
          using signature_type = argument_dependent;

          /// Whether or not the coroutine is noexcept.
          constexpr static bool is_noexcept = argument_dependent;

          /// The error type of the coroutine. @c void for noexcept.
          using error_type = argument_dependent;

          /// Completion handler type used by async_resume.
          using completion_handler = argument_dependent;
        };

#else // defined(GENERATING_DOCUMENTATION)

        template<typename Yield, typename Return, typename Executor>
        struct coro_traits {
            using input_type = void;
            using yield_type = Yield;
            using return_type = Return;
            using result_type = detail::coro_result_t<yield_type, return_type>;
            using signature_type = result_type();
            constexpr static bool is_noexcept = false;
            using error_type = std::conditional_t<is_noexcept, void, std::exception_ptr>;
            using completion_handler = detail::coro_handler_t<result_type, is_noexcept>;
        };

        template<typename T, typename Return, typename Executor>
        struct coro_traits<T(), Return, Executor> {
            using input_type = void;
            using yield_type = T;
            using return_type = Return;
            using result_type = detail::coro_result_t<yield_type, return_type>;
            using signature_type = result_type();
            constexpr static bool is_noexcept = false;
            using error_type = std::conditional_t<is_noexcept, void, std::exception_ptr>;
            using completion_handler = detail::coro_handler_t<result_type, is_noexcept>;
        };

        template<typename T, typename Return, typename Executor>
        struct coro_traits<T() noexcept, Return, Executor> {
            using input_type = void;
            using yield_type = T;
            using return_type = Return;
            using result_type = detail::coro_result_t<yield_type, return_type>;
            using signature_type = result_type();
            constexpr static bool is_noexcept = true;
            using error_type = std::conditional_t<is_noexcept, void, std::exception_ptr>;
            using completion_handler = detail::coro_handler_t<result_type, is_noexcept>;
        };

        template<typename T, typename U, typename Return, typename Executor>
        struct coro_traits<T(U), Return, Executor> {
            using input_type = U;
            using yield_type = T;
            using return_type = Return;
            using result_type = detail::coro_result_t<yield_type, return_type>;
            using signature_type = result_type(input_type);
            constexpr static bool is_noexcept = false;
            using error_type = std::conditional_t<is_noexcept, void, std::exception_ptr>;
            using completion_handler = detail::coro_handler_t<result_type, is_noexcept>;
        };

        template<typename T, typename U, typename Return, typename Executor>
        struct coro_traits<T(U) noexcept, Return, Executor> {
            using input_type = U;
            using yield_type = T;
            using return_type = Return;
            using result_type = detail::coro_result_t<yield_type, return_type>;
            using signature_type = result_type(input_type);
            constexpr static bool is_noexcept = true;
            using error_type = std::conditional_t<is_noexcept, void, std::exception_ptr>;
            using completion_handler = detail::coro_handler_t<result_type, is_noexcept>;
        };

        template<typename Executor>
        struct coro_traits<void() noexcept, void, Executor> {
            using input_type = void;
            using yield_type = void;
            using return_type = void;
            using result_type = detail::coro_result_t<yield_type, return_type>;
            using signature_type = result_type(input_type);
            constexpr static bool is_noexcept = true;
            using error_type = std::conditional_t<is_noexcept, void, std::exception_ptr>;
            using completion_handler = detail::coro_handler_t<result_type, is_noexcept>;
        };

#endif // defined(GENERATING_DOCUMENTATION)

    } // namespace experimental
} // namespace asio

#endif // ASIO_EXPERIMENTAL_DETAIL_CORO_TRAITS_HPP
